package pCloud.client;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

import pCloud.config.PCloudConfig;
import pCloud.config.PCloudDirectRequestConfig;
import pCloud.config.PCloudRequestConfig;
import pCloud.exception.InvalidClientRequest;
import pCloud.message.ClientRequest;
import pCloud.message.DatabaseMessage;
import pCloud.message.DatabaseRequest;
import pCloud.message.Message;
import pCloud.message.NumberMessage;
import pCloud.message.PolicyFile;
import pCloud.message.PolicyManagementRequest;
import pCloud.message.PurgeCacheRequest;
import pCloud.message.RequestFile;
import pCloud.message.RequestedResource;
import pCloud.server.ProxyServer;
import pCloud.service.ClientProxyServerProtHandle;
import pCloud.transport.Communicator;
import pCloud.transport.SocketCommunicator;
import pCloud.transport.SocketTransportHandle;

public class Client {

	Connector connector;
	InetSocketAddress proxyServerAddress;

	Communicator communicator;
	int clientId;
	Logger logger;

	String configFile = PCloudConfig.DEFAULT_CONFIG_FILE;
	InetSocketAddress localAddress;

	public Client(int clientId, String configFile) throws Exception {
		this.clientId = clientId;
		this.communicator = new SocketCommunicator();
		this.configFile = configFile;
		init();
		startConnection();
	}

	void init() throws Exception {
		PCloudConfig.init(this.configFile);

		logger = initLog("Client" + clientId);
		communicator.init(logger);

		String[] addr = PCloudConfig.getString(
				PCloudConfig.PCLOUD_PROXY_SERVER_ADDRESS).split(" ");
		String hostName = addr[0];
		int port = new Integer(addr[1]).intValue();

		connector = new ClientProxyServerConnector(hostName, port,
				this.communicator);

		proxyServerAddress = new InetSocketAddress(hostName, port);
	}

	Logger initLog(String name) {
		Logger logger = Logger.getLogger(name);
		SimpleLayout layout = new SimpleLayout();

		FileAppender appender = null;
		try {
			appender = new FileAppender(layout,
					PCloudConfig.getString(PCloudConfig.PCLOUD_CLIENT_LOG),
					false);
		} catch (Exception e) {
			System.err.println("Error initializing log file for " + name);
			e.printStackTrace();
		}

		logger.addAppender(appender);
		return logger;
	}

	private void startConnection() throws Exception {
		connector.connect();
		this.communicator.addConnection(connector.protocolHandler);
		this.localAddress = (InetSocketAddress) connector.protocolHandler
				.getTransportHanlde().getLocalAddress();
	}

	/**
	 * Loading the policyFile, to specified server name
	 * 
	 * @param policyFileName
	 * @throws Exception
	 */
	public void loadPolicy(String policyFileName, String dataId,
			String timeOutputFile) throws Exception {

		communicator.getLogger().debug(
				"Uploading policy " + policyFileName + " onto dataset "
						+ dataId);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(timeOutputFile);

		// InetSocketAddress isa = new InetSocketAddress(serverIp, serverPort);
		PolicyManagementRequest pmr = null;

		PolicyFile policyFile = new PolicyFile();
		policyFile.initializeContentFromFile(policyFileName);
		pmr = new PolicyManagementRequest(PolicyManagementRequest.ADD, dataId,
				policyFile);

		Message msg = new Message(this.localAddress, null, pmr,
				Message.CLIENT_TO_PROXY_MSG);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(proxyServerAddress, msg);
	}

	public void removePolicy(int policyIndex, String dataId,
			String timeOutputFile) throws Exception {
		communicator.getLogger()
				.debug("Removing policy from " + dataId + " with index "
						+ policyIndex);

		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(timeOutputFile);

		// InetSocketAddress isa = new InetSocketAddress(serverIp, serverPort);
		PolicyManagementRequest pmr = null;

		pmr = new PolicyManagementRequest(PolicyManagementRequest.REMOVE,
				dataId, policyIndex);

		Message msg = new Message(this.localAddress, null, pmr,
				Message.CLIENT_TO_PROXY_MSG);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(proxyServerAddress, msg);
	}

	/**
	 * Fetching active policies in remote servers
	 * 
	 * @param serverIp
	 * @param serverPort
	 * @throws Exception
	 */
	public void queryPolicy(String dataId, String timeOutputFile)
			throws Exception {
		this.communicator.getLogger().debug(
				"Query active policies on remote server " + dataId);
		// InetSocketAddress isa = new InetSocketAddress(serverIp, serverPort);

		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(timeOutputFile);

		PolicyManagementRequest pmr = new PolicyManagementRequest(
				PolicyManagementRequest.QUERY, dataId, 0);
		Message msg = new Message(this.localAddress, null, pmr,
				Message.CLIENT_TO_PROXY_MSG);

		// System.out.println("Query: " + System.currentTimeMillis());
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(proxyServerAddress, msg);
	}

	public void requestToPurgeCache(String timeOutputFile) throws Exception {
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(timeOutputFile);
		Message msg = new Message(this.localAddress, null,
				new PurgeCacheRequest(), Message.CLIENT_TO_PROXY_MSG);
		this.communicator.getLogger().debug("Sending request to purge cache");
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(proxyServerAddress, msg);
	}

	public void requestDataFromServer(String requestConfigFile,
			boolean printingData, String timeOutputFile) throws Exception {
		PCloudRequestConfig.init(requestConfigFile);

		ArrayList<RequestedResource> resources = PCloudRequestConfig
				.parseConfig();
		this.communicator.getLogger().debug(
				"Constructing client request for " + resources.size()
						+ " resources");

		// setting the printing data option on the protocol handler.
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setPrintingDataOption(printingData);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(timeOutputFile);

		ClientRequest clientRequest = new ClientRequest();

		int nReplies = resources.size();

		RequestedResource rr;
		for (int i = 0; i < resources.size(); i++) {
			rr = resources.get(i);
			clientRequest.addRequestedResource(rr, rr.getJoiningColumn());
			if (rr.getJoiningColumn() != null) // if there's joining, expect
												// only 1 reply
				nReplies = 1;
		}

		// requestFile.initializeContentFromFile("requestFile.xml");
		Message msg = new Message(this.localAddress, null, clientRequest,
				Message.CLIENT_TO_PROXY_MSG);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setNumberOfReplies(nReplies);
		communicator.send(proxyServerAddress, msg);
	}

	public static void main(String[] args) throws Exception {

		if (args.length < 1)
			throw new InvalidClientRequest("Config file needed");

		if (args[1].equals("-help")) {
			System.out
					.println("Client <configFile> -load <policyFile> <dataId> [-tOutput <fileName>]: adding policy. "
							+ "The -tOutput option means time is appended to the file. Ignored means time is sent to the"
							+ "output stream");
			System.out
					.println("Client <configFile> -remove <policyIndex> <dataId> [-tOutput <fileName>]: remove policy");
			System.out
					.println("Client <configFile> -query <dataId> [-tOutput <fileName>]: query active policies in remote data servers");
			System.out.println("Client <configFile> purgeCache");
			System.out
					.println("Client <configFile> -request <requestConfigFile> [-noData] [-tOutput <fileName>]: request data, "
							+ "parameters read from the config file. The [-noData] option ");
			return;
		} else if (args[1].equals("-load")) {
			if (args.length < 4)
				throw new InvalidClientRequest(
						"Loading operation needs at least 4 parameters");

			Client client = new Client(0, args[0]);

			String policyFile = args[2];

			String timeOutputFile = null;
			if (args.length == 6 && args[4].equals("-tOutput"))
				timeOutputFile = args[5];

			client.loadPolicy(policyFile, args[3], timeOutputFile);
			return;

		} else if (args[1].equals("-remove")) {
			if (args.length < 4)
				throw new InvalidClientRequest(
						"Removing policy needs 4 parameters");
			Client client = new Client(0, args[0]);

			int policyIndex = new Integer(args[2]).intValue();

			// String[] address = args[3].split(":");
			String timeOutputFile = null;
			if (args.length == 6 && args[4].equals("-tOutput"))
				timeOutputFile = args[5];
			client.removePolicy(policyIndex, args[3], timeOutputFile);
			return;

		} else if (args[1].equals("-query")) {
			if (args.length < 5)
				throw new InvalidClientRequest(
						"Query operation needs 3 parameters");
			// String[] address = args[2].split(":");
			Client client = new Client(0, args[0]);
			String timeOutputFile = null;
			if (args.length == 5 && args[3].equals("-tOutput"))
				timeOutputFile = args[4];
			client.queryPolicy(args[2], timeOutputFile);
			return;

		} else if (args[1].equals("-request")) {
			if (args.length < 3)
				throw new InvalidClientRequest(
						"Request operation needs at least 3 parameters");
			Client client = new Client(0, args[0]);

			boolean isNoData = true;
			String timeOutputFile = null;

			for (int i = 2; i < args.length; i++) {
				if (args[i].equals("-noData"))
					isNoData = false;
				if (args[i].equals("-tOutput"))
					timeOutputFile = args[i + 1];
			}

			client.requestDataFromServer(args[2], isNoData, timeOutputFile);
			return;
		} 
		else if (args[1].equals("purgeCache")){
			Client client = new Client(0, args[0]);
			if (args.length >1 && args[2].equals("-tOutput"))
			client.requestToPurgeCache(args[3]);
		}
		else
			throw new InvalidClientRequest(
					"Client request is no well-formed. Use -help for details");
	}
}
